package com.idea.errorcode.doc;

import com.idea.errorcode.api.IErrorCode;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;

import javax.annotation.PostConstruct;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: 沉香
 * @date: 2025/04/13
 * @description: 异常码文档生成器
 */
public class ErrorDocGenerator {

    private static final String CONFIG_PATH = "error-code-rules.yml";

    @PostConstruct
    public void generateOnStartup() throws Exception {
        List<HierarchyConfig> hierarchyConfigs = loadHierarchyConfig();
        List<ErrorCodeDoc> errorCodeDocs = loadErrorCodeEnums();
        generateHtmlDocs(errorCodeDocs, hierarchyConfigs);
    }

    public List<ErrorCodeDoc> loadErrorCodeEnums() {
        List<ErrorCodeDoc> errorCodeDocs = new ArrayList<>();
        try (InputStream is = Thread.currentThread()
                .getContextClassLoader()
                .getResourceAsStream("META-INF/error-codes")) {
            if (is == null) {
                return errorCodeDocs;
            }
            new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))
                    .lines()
                    .map(String::trim)
                    .filter(line -> !line.isEmpty())
                    .forEach(e -> {
                        errorCodeDocs.addAll(processErrorClass(e));
                    });
        } catch (Exception e) {
            System.err.println("读取异常码枚举类元数据文件失败,路径[META-INF/error-codes],异常原因：" + e.getMessage());
            return errorCodeDocs;
        }
        return errorCodeDocs;
    }

    private List<ErrorCodeDoc> processErrorClass(String className) {
        List<ErrorCodeDoc> errorCodeDocs = new ArrayList<>();
        try {
            Class<?> clazz = Class.forName(className);
            if (clazz.isEnum() && IErrorCode.class.isAssignableFrom(clazz)) {
                errorCodeDocs = Arrays.stream(clazz.getEnumConstants())
                        .map(enumConst -> parseEnumConstant((Enum<?>) enumConst))
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            System.err.println("加载文档失败: " + e.getMessage());
            return errorCodeDocs;
        }
        return errorCodeDocs;
    }

    private List<HierarchyConfig> loadHierarchyConfig() throws Exception {
        try (InputStream is = new ClassPathResource(CONFIG_PATH).getInputStream()) {
            Map<String, Object> yamlMap = new Yaml().load(is);
            return parseHierarchy(yamlMap);
        }
    }

    private List<HierarchyConfig> parseHierarchy(Map<String, Object> yamlMap) {
        List<HierarchyConfig> hierarchy = new ArrayList<>();

        if (yamlMap == null || yamlMap.isEmpty()) {
            return hierarchy;
        }

        List<Map<String, Object>> hierarchyList =
                (List<Map<String, Object>>) yamlMap.get("hierarchy");

        if (hierarchyList == null) {
            return hierarchy;
        }

        for (Map<String, Object> item : hierarchyList) {
            HierarchyConfig config = new HierarchyConfig();
            config.setName((String) item.get("name"));
            config.setLength((Integer) item.get("length"));

            // 解析可选的 mapping 配置
            if (item.containsKey("mapping")) {
                Map<String, String> mapping = new LinkedHashMap<>();
                Map<String, String> rawMapping =
                        (Map<String, String>) item.get("mapping");
                if (rawMapping != null) {
                    mapping.putAll(rawMapping);
                }
                config.setMapping(mapping);
            }
            hierarchy.add(config);
        }
        return hierarchy;
    }

    private ErrorCodeDoc parseEnumConstant(Enum<?> enumConstant) {
        ErrorCodeDoc doc = new ErrorCodeDoc();
        try {
            // 1. 获取基础信息
            doc.setClassName(enumConstant.getDeclaringClass().getName());
            doc.setEnumName(enumConstant.name());
            doc.setCode(this.invokeMethodSafely(enumConstant, "getErrorCode"));
            doc.setMessage(this.invokeMethodSafely(enumConstant, "getMessage"));
            doc.setUserTip(this.invokeOptionalMethod(enumConstant, "getUserTip"));
            doc.setMessageKey(this.invokeOptionalMethod(enumConstant, "getMessageKey"));
        } catch (Exception e) {
            throw new RuntimeException("解析枚举失败: " + enumConstant, e);
        }
        return doc;
    }

    /**
     * 安全调用必须存在的方法
     */
    private String invokeMethodSafely(Enum<?> enumConstant, String methodName) throws Exception {
        try {
            Method method = enumConstant.getClass().getMethod(methodName);
            String result = (String) method.invoke(enumConstant);
            return result != null ? result : "";
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("枚举类必须实现 " + methodName + " 方法");
        }
    }

    /**
     * 调用可选方法（不存在时返回空字符串）
     */
    private String invokeOptionalMethod(Enum<?> enumConstant, String methodName) {
        try {
            Method method = enumConstant.getClass().getMethod(methodName);
            String result = (String) method.invoke(enumConstant);
            return result != null ? result : "";
        } catch (NoSuchMethodException e) {
            return "";
        } catch (Exception e) {
            throw new RuntimeException("调用可选方法失败: " + methodName, e);
        }
    }


    private void generateHtmlDocs(List<ErrorCodeDoc> docs, List<HierarchyConfig> hierarchy) {
        try {
            // 获取classpath根目录路径
            URL resource = getClass().getClassLoader().getResource("");
            Path classpathRoot = Paths.get(resource.toURI());
            // 构建static目录路径
            Path staticDir = classpathRoot.resolve("static");
            // 创建static目录（如果不存在）
            if (!Files.exists(staticDir)) {
                Files.createDirectories(staticDir);
            }
            // 构建最终输出路径
            Path outputPath = staticDir.resolve("error-codes.html");
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("<!DOCTYPE html>\n" +
                    "<html>");
            // HTML 头部
            writeHtmlHeader(stringBuilder);
            // JavaScript 逻辑
            writeJsLogic(stringBuilder, docs, hierarchy);
            // HTML 尾部
            stringBuilder.append("</body>\n");
            stringBuilder.append("</html>\n");
            Files.write(outputPath, stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("生成异常码文档失败,异常原因：" + e.getMessage());
        }
    }

    private void writeData(StringBuilder sb, List<ErrorCodeDoc> docs, List<HierarchyConfig> hierarchy) {
        // 数据注入
        sb.append("    const allData = " + toJsonErrorCodeDoc(docs) + ";\n");
        sb.append("    const hierarchyConfig = " + toJson(hierarchy) + ";\n");
    }

    private void writeHtmlHeader(StringBuilder sb) {
        sb.append("<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>错误码文档系统</title>\n" +
                "    <style>\n" +
                "        :root {\n" +
                "            --primary-color: #409EFF;\n" +
                "            --hover-color: #79BBFF;\n" +
                "            --border-color: #EBEEF5;\n" +
                "            --bg-color: #F8F9FA;\n" +
                "        }\n" +
                "\n" +
                "        body {\n" +
                "            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n" +
                "            margin: 20px;\n" +
                "            color: #606266;\n" +
                "        }\n" +
                "\n" +
                "        #search {\n" +
                "            width: 100%;\n" +
                "            max-width: 400px;\n" +
                "            padding: 10px 15px;\n" +
                "            border: 1px solid var(--border-color);\n" +
                "            border-radius: 4px;\n" +
                "            margin-bottom: 20px;\n" +
                "            transition: box-shadow 0.3s;\n" +
                "        }\n" +
                "\n" +
                "        #search:focus {\n" +
                "            outline: none;\n" +
                "            box-shadow: 0 0 5px rgba(64, 158, 255, 0.3);\n" +
                "        }\n" +
                "\n" +
                "        .data-table {\n" +
                "            width: 100%;\n" +
                "            border-collapse: collapse;\n" +
                "            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n" +
                "            background: white;\n" +
                "        }\n" +
                "\n" +
                "        .data-table th {\n" +
                "            background: var(--bg-color);\n" +
                "            padding: 12px 15px;\n" +
                "            text-align: left;\n" +
                "            font-weight: 500;\n" +
                "            border-bottom: 2px solid var(--border-color);\n" +
                "        }\n" +
                "\n" +
                "        .data-table td {\n" +
                "            padding: 12px 15px;\n" +
                "            border-bottom: 1px solid var(--border-color);\n" +
                "        }\n" +
                "\n" +
                "        .data-table tr:hover {\n" +
                "            background-color: rgba(64, 158, 255, 0.05);\n" +
                "        }\n" +
                "\n" +
                "        .tree-arrow {\n" +
                "            cursor: pointer;\n" +
                "            display: inline-block;\n" +
                "            width: 18px;\n" +
                "            height: 18px;\n" +
                "            text-align: center;\n" +
                "            margin-right: 5px;\n" +
                "            transition: transform 0.2s;\n" +
                "        }\n" +
                "\n" +
                "        .tree-arrow.expanded {\n" +
                "            transform: rotate(90deg);\n" +
                "        }\n" +
                "\n" +
                "        .pagination {\n" +
                "            margin-top: 20px;\n" +
                "            display: flex;\n" +
                "            gap: 15px;\n" +
                "            align-items: center;\n" +
                "        }\n" +
                "\n" +
                "        .pagination button {\n" +
                "            padding: 8px 16px;\n" +
                "            border-radius: 4px;\n" +
                "            border: 1px solid var(--border-color);\n" +
                "            background: white;\n" +
                "            color: var(--primary-color);\n" +
                "            cursor: pointer;\n" +
                "        }\n" +
                "\n" +
                "        .pagination button:disabled {\n" +
                "            color: #C0C4CC;\n" +
                "            cursor: not-allowed;\n" +
                "        }\n" +
                "\n" +
                "        .pagination button:hover:not(:disabled) {\n" +
                "            background: var(--primary-color);\n" +
                "            color: white;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent {\n" +
                "            padding-left: 30px;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent.level-1 {\n" +
                "            padding-left: 45px;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent.level-2 {\n" +
                "            padding-left: 60px;\n" +
                "        }\n" +
                "\n" +
                "        .empty-tip {\n" +
                "            text-align: center;\n" +
                "            color: #909399;\n" +
                "            padding: 40px 0;\n" +
                "        }\n" +
                "\n" +
                "        .code-segment {\n" +
                "            color: #666;\n" +
                "            margin-right: 8px;\n" +
                "        }\n" +
                "\n" +
                "        .tree-indent {\n" +
                "            display: inline-block;\n" +
                "            width: 24px;\n" +
                "            vertical-align: top;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent {\n" +
                "            padding-left: 30px;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent.level-1 {\n" +
                "            padding-left: 54px;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent.level-2 {\n" +
                "            padding-left: 78px;\n" +
                "        }\n" +
                "\n" +
                "        .level-indent.level-3 {\n" +
                "            padding-left: 102px;\n" +
                "        }\n" +
                "\n" +
                "        /* 调整箭头位置 */\n" +
                "        .tree-arrow {\n" +
                "            vertical-align: top;\n" +
                "            margin-right: 8px;\n" +
                "        }\n" +
                "\n" +
                "        .highlight {\n" +
                "            background: #ffeb3b;\n" +
                "            padding: 2px 0;\n" +
                "            border-radius: 2px;\n" +
                "            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n" +
                "        }\n" +
                "\n" +
                "        .data-table th:nth-child(5),\n" +
                "        .data-table td:nth-child(5) {\n" +
                "            width: 18%;\n" +
                "        }\n" +
                "    </style>\n" +
                "</head>");
        sb.append("<body>\n" +
                "<input type=\"text\" id=\"search\" placeholder=\"\uD83D\uDD0D 输入错误码、描述或类名进行搜索...\">\n" +
                "<div id=\"data-container\"></div>\n" +
                "<div id=\"pagination\"></div>\n");
    }

    private void writeJsLogic(StringBuilder sb, List<ErrorCodeDoc> docs, List<HierarchyConfig> hierarchy) {
        sb.append("<script>\n");
        writeData(sb, docs, hierarchy);
        sb.append("    const state = {\n" +
                "        pageSize: 10,\n" +
                "        currentPage: 1,\n" +
                "        expandedCodes: new Set(),\n" +
                "        flatData: [],\n" +
                "        currentKeyword: '',\n" +
                "    };\n" +
                "\n" +
                "    // 初始化\n" +
                "    function init() {\n" +
                "        const treeData = buildHierarchyTree(allData);\n" +
                "        state.flatData = flattenTree(treeData);\n" +
                "        render();\n" +
                "        setupEventListeners();\n" +
                "    }\n" +
                "\n" +
                "    function validateHierarchyConfig() {\n" +
                "        if (!hierarchyConfig.length) return false;\n" +
                "\n" +
                "        // 检查所有错误码长度是否一致\n" +
                "        const codeLengths = new Set(allData.map(d => d.code.length));\n" +
                "        if (codeLengths.size !== 1) return false;\n" +
                "\n" +
                "        // 计算配置总长度\n" +
                "        const totalConfigLength = hierarchyConfig.reduce((sum, c) => sum + c.length, 0);\n" +
                "        return Array.from(codeLengths)[0] === totalConfigLength;\n" +
                "    }\n" +
                "\n" +
                "    // 构建层级树（关键修改部分）\n" +
                "    function buildHierarchyTree(data) {\n" +
                "\n" +
                "        // 无配置或校验失败时直接返回叶子节点\n" +
                "        if (!validateHierarchyConfig()) {\n" +
                "            return data.map(item => ({\n" +
                "                ...wrapLeafNode(item),\n" +
                "                depth: 0,\n" +
                "                path: item.code,\n" +
                "                isLeaf: true\n" +
                "            }));\n" +
                "        }\n" +
                "\n" +
                "        if (!hierarchyConfig.length) return data.map(wrapLeafNode);\n" +
                "\n" +
                "        const root = {children: []};\n" +
                "\n" +
                "        data.forEach(item => {\n" +
                "            let code = item.code;\n" +
                "            let currentNode = root;\n" +
                "            const pathSegments = [];\n" +
                "\n" +
                "            hierarchyConfig.forEach((config, depth) => {\n" +
                "                // 判断是否是最后一个层级\n" +
                "                const isLastLevel = depth === hierarchyConfig.length - 1;\n" +
                "\n" +
                "                // 如果是最后一个层级，直接创建叶子节点\n" +
                "                if (isLastLevel) {\n" +
                "                    const leafNode = {\n" +
                "                        ...wrapLeafNode(item),\n" +
                "                        seg: code, // 保留完整剩余code\n" +
                "                        path: pathSegments.map(p => p.seg).join('') + code,\n" +
                "                        depth: depth + 1,\n" +
                "                        isLeaf: true,\n" +
                "                        originalData: item\n" +
                "                    };\n" +
                "                    currentNode.children.push(leafNode);\n" +
                "                    return; // 跳过后续处理\n" +
                "                }\n" +
                "\n" +
                "                // 正常处理非最后层级\n" +
                "                const seg = code.substring(0, config.length);\n" +
                "                code = code.substring(config.length);\n" +
                "\n" +
                "                const display = config.mapping?.[seg] || seg;\n" +
                "                pathSegments.push({seg, display, depth, configName: config.name});\n" +
                "\n" +
                "                let child = currentNode.children.find(c => c.seg === seg);\n" +
                "                if (!child) {\n" +
                "                    child = {\n" +
                "                        seg,\n" +
                "                        display,\n" +
                "                        depth,\n" +
                "                        configName: config.name,\n" +
                "                        path: pathSegments.map(p => p.seg).join(''),\n" +
                "                        children: [],\n" +
                "                        isLeaf: false,\n" +
                "                        originalData: null\n" +
                "                    };\n" +
                "                    currentNode.children.push(child);\n" +
                "                }\n" +
                "                currentNode = child;\n" +
                "            });\n" +
                "        });\n" +
                "\n" +
                "        return root.children;\n" +
                "    }\n" +
                "\n" +
                "    function wrapLeafNode(item) {\n" +
                "        return {\n" +
                "            seg: item.code,\n" +
                "            display: item.code,\n" +
                "            depth: hierarchyConfig.length,\n" +
                "            configName: \"具体错误\",\n" +
                "            path: item.code,\n" +
                "            originalData: {\n" +
                "                ...item,\n" +
                "                enumName: item.enumName || '-'\n" +
                "            },\n" +
                "            isLeaf: true,\n" +
                "            children: []\n" +
                "        };\n" +
                "    }\n" +
                "\n" +
                "    // 扁平化树结构\n" +
                "    function flattenTree(nodes, level = 0) {\n" +
                "        // 无层级配置时直接返回\n" +
                "        // if (!validateHierarchyConfig()) {\n" +
                "        //     return nodes.map(n => ({ ...n, level: 0 }));\n" +
                "        // }\n" +
                "\n" +
                "        return nodes.reduce((acc, node) => {\n" +
                "            acc.push({...node, level});\n" +
                "\n" +
                "            if (node.children && state.expandedCodes.has(node.path)) {\n" +
                "                acc.push(...flattenTree(node.children, level + 1));\n" +
                "            }\n" +
                "            return acc;\n" +
                "        }, []);\n" +
                "    }\n" +
                "\n" +
                "    // 渲染主界面\n" +
                "    function render() {\n" +
                "        const start = (state.currentPage - 1) * state.pageSize;\n" +
                "        const pageData = state.flatData.slice(start, start + state.pageSize);\n" +
                "\n" +
                "        const tableHtml = `\n" +
                "        <table class=\"data-table\">\n" +
                "            <thead>\n" +
                "                <tr>\n" +
                "                    <th>错误码</th>\n" +
                "                    <th>描述</th>\n" +
                "                    <th>用户提示</th>\n" +
                "                    <th>所属类</th>\n" +
                "                    <th>枚举类名</th>\n" +
                "                    <th>国际化消息键</th>\n" +
                "                </tr>\n" +
                "            </thead>\n" +
                "            <tbody>\n" +
                "                ${pageData.map(item => `\n" +
                "                    <tr class=\"${getIndentClass(item)}\">\n" +
                "                        <td>${renderCodeColumn(item)}</td>\n" +
                "                        <td>${renderDescription(item)}</td>\n" +
                "                        <td>${renderUserTip(item)}</td>\n" +
                "                        <td>${renderClassName(item)}</td>\n" +
                "                        <td>${renderEnumName(item)}</td>\n" +
                "                        <td>${renderMessageKey(item)}</td>\n" +
                "                    </tr>\n" +
                "                `).join('')}\n" +
                "            </tbody>\n" +
                "        </table>\n" +
                "    `;\n" +
                "\n" +
                "        document.getElementById('data-container').innerHTML =\n" +
                "            state.flatData.length ? tableHtml : '<div class=\"empty-tip\">没有找到匹配的结果</div>';\n" +
                "\n" +
                "        renderPagination();\n" +
                "    }\n" +
                "\n" +
                "    // 新增缩进class计算\n" +
                "    function getIndentClass(item) {\n" +
                "        const depth = calculateNodeDepth(item);\n" +
                "        return depth > 0 ? `level-indent level-${depth - 1}` : '';\n" +
                "    }\n" +
                "\n" +
                "    function renderCodeColumn(item) {\n" +
                "        if (!hierarchyConfig.length || !validateHierarchyConfig()) {\n" +
                "            const code = item.originalData.code;\n" +
                "            return highlightText(code, state.currentKeyword);\n" +
                "        }\n" +
                "\n" +
                "        const depth = calculateNodeDepth(item);\n" +
                "        let indent = '';\n" +
                "        for (let i = 0; i < depth; i++) {\n" +
                "            indent += `<span class=\"tree-indent\"></span>`;\n" +
                "        }\n" +
                "\n" +
                "        let codeParts = [];\n" +
                "        if (item.children?.length) {\n" +
                "            const isExpanded = state.expandedCodes.has(item.path);\n" +
                "            codeParts.push(`\n" +
                "                <span class=\"tree-arrow ${isExpanded ? 'expanded' : ''}\"\n" +
                "                      onclick=\"toggleExpand('${item.path}', event)\">▶</span>\n" +
                "            `);\n" +
                "        }\n" +
                "\n" +
                "        const codeValue = !item.isLeaf ? item.seg : item.path;\n" +
                "        codeParts.push(highlightText(codeValue, state.currentKeyword));\n" +
                "\n" +
                "        return `${indent}${codeParts.join('')}`;\n" +
                "    }\n" +
                "\n" +
                "    // 新增通用高亮方法\n" +
                "    function highlightText(text, keyword) {\n" +
                "        if (!keyword || !text) return text;\n" +
                "        const escaped = escapeRegExp(keyword);\n" +
                "        // 使用'g'标志而非'gi'实现大小写敏感\n" +
                "        return text.toString().replace(\n" +
                "            new RegExp(escaped, 'g'),\n" +
                "            match => `<span class=\"highlight\">${match}</span>`\n" +
                "        );\n" +
                "    }\n" +
                "\n" +
                "    // 正则表达式特殊字符转义\n" +
                "    function escapeRegExp(string) {\n" +
                "        return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n" +
                "    }\n" +
                "\n" +
                "    // 新增节点深度计算方法\n" +
                "    function calculateNodeDepth(node) {\n" +
                "        if (!node.path || !hierarchyConfig) return 0;\n" +
                "\n" +
                "        // 通过path长度计算实际层级\n" +
                "        let currentLength = 0;\n" +
                "        let depth = 0;\n" +
                "        hierarchyConfig.some(config => {\n" +
                "            currentLength += config.length;\n" +
                "            depth++;\n" +
                "            return currentLength >= node.path.length;\n" +
                "        });\n" +
                "\n" +
                "        return depth - 1; // 返回实际树层级\n" +
                "    }\n" +
                "\n" +
                "    function renderDescription(item) {\n" +
                "        let content = item.isLeaf ?\n" +
                "            (item.originalData.message || '-') :\n" +
                "            `${item.display}（${item.configName}）`;\n" +
                "\n" +
                "        return highlightText(content, state.currentKeyword);\n" +
                "    }\n" +
                "\n" +
                "    function renderUserTip(item) {\n" +
                "        const text = item.isLeaf ? (item.originalData.userTip || '-') : '-';\n" +
                "        return highlightText(text, state.currentKeyword);\n" +
                "    }\n" +
                "\n" +
                "    function renderClassName(item) {\n" +
                "        const text = item.isLeaf ? item.originalData.className : '-';\n" +
                "        return highlightText(text, state.currentKeyword);\n" +
                "    }\n" +
                "\n" +
                "    function renderEnumName(item) {\n" +
                "        const text = item.isLeaf ? (item.originalData.enumName || '-') : '-';\n" +
                "        return highlightText(text, state.currentKeyword);\n" +
                "    }\n" +
                "\n" +
                "    function renderMessageKey(item) {\n" +
                "        const text = item.isLeaf ? (item.originalData.messageKey || '-') : '-';\n" +
                "        return highlightText(text, state.currentKeyword);\n" +
                "    }\n" +
                "\n" +
                "    // 分页控制\n" +
                "    function renderPagination() {\n" +
                "        const totalPages = Math.ceil(state.flatData.length / state.pageSize);\n" +
                "        document.getElementById('pagination').innerHTML = `\n" +
                "        <div class=\"pagination\">\n" +
                "            <button ${state.currentPage === 1 ? 'disabled' : ''}\n" +
                "                onclick=\"changePage(${state.currentPage - 1})\">上一页</button>\n" +
                "            <span>第 ${state.currentPage} 页 / 共 ${totalPages} 页</span>\n" +
                "            <button ${state.currentPage >= totalPages ? 'disabled' : ''}\n" +
                "                onclick=\"changePage(${state.currentPage + 1})\">下一页</button>\n" +
                "        </div>\n" +
                "    `;\n" +
                "    }\n" +
                "\n" +
                "    // 事件处理\n" +
                "    function toggleExpand(path, event) {\n" +
                "        event.stopPropagation();\n" +
                "        state.expandedCodes.has(path)\n" +
                "            ? state.expandedCodes.delete(path)\n" +
                "            : state.expandedCodes.add(path);\n" +
                "\n" +
                "        refreshData();\n" +
                "    }\n" +
                "\n" +
                "    // 新增数据刷新方法\n" +
                "    function refreshData() {\n" +
                "        const allNodes = buildHierarchyTree(allData);\n" +
                "\n" +
                "        if (state.currentKeyword) {\n" +
                "            const {matchPaths} = findMatchPaths(allNodes, state.currentKeyword);\n" +
                "            state.flatData = filterAndFlatten(allNodes, matchPaths);\n" +
                "        } else {\n" +
                "            state.flatData = flattenTree(allNodes);\n" +
                "        }\n" +
                "\n" +
                "        render();\n" +
                "    }\n" +
                "\n" +
                "    function changePage(newPage) {\n" +
                "        state.currentPage = Math.max(1,\n" +
                "            Math.min(newPage, Math.ceil(state.flatData.length / state.pageSize)));\n" +
                "        render();\n" +
                "    }\n" +
                "\n" +
                "    function handleSearch() {\n" +
                "        const keyword = document.getElementById('search').value.trim();\n" +
                "        state.currentKeyword = keyword;\n" +
                "        state.expandedCodes.clear();\n" +
                "\n" +
                "        const allNodes = buildHierarchyTree(allData);\n" +
                "        const {matchPaths} = findMatchPaths(allNodes, keyword);\n" +
                "\n" +
                "        // 执行层级过滤\n" +
                "        const filteredTree = filterWithHierarchy(allNodes, matchPaths);\n" +
                "        state.flatData = flattenTree(filteredTree);\n" +
                "\n" +
                "        // 自动展开必要层级\n" +
                "        Array.from(matchPaths).forEach(p => {\n" +
                "            if (p.includes('/')) {\n" +
                "                const parentPath = p.split('/').slice(0, -1).join('/');\n" +
                "                state.expandedCodes.add(parentPath);\n" +
                "            }\n" +
                "        });\n" +
                "\n" +
                "        state.currentPage = 1;\n" +
                "        render();\n" +
                "    }\n" +
                "\n" +
                "    // 精确层级过滤方法\n" +
                "    function filterWithHierarchy(nodes, matchPaths) {\n" +
                "        return nodes.reduce((acc, node) => {\n" +
                "            // 判断条件优化：匹配当前节点或其任意层级父子关系\n" +
                "            const isMatched = Array.from(matchPaths).some(p =>\n" +
                "                p === node.path ||               // 精确匹配\n" +
                "                p.startsWith(node.path + '/') || // 是子路径的父级\n" +
                "                node.path.startsWith(p + '/')    // 是父路径的子级\n" +
                "            );\n" +
                "\n" +
                "            if (isMatched) {\n" +
                "                const cloned = {...node, children: []};\n" +
                "                // 自动展开必要层级\n" +
                "                if (node.children) {\n" +
                "                    cloned.children = filterWithHierarchy(node.children, matchPaths);\n" +
                "                    if (cloned.children.length > 0) {\n" +
                "                        state.expandedCodes.add(node.path);\n" +
                "                    }\n" +
                "                }\n" +
                "                acc.push(cloned);\n" +
                "            }\n" +
                "            return acc;\n" +
                "        }, []);\n" +
                "    }\n" +
                "\n" +
                "    // 整合后的过滤扁平化方法\n" +
                "    function filterAndFlatten(nodes, matchPaths) {\n" +
                "        return nodes.reduce((acc, node) => {\n" +
                "            // 精确匹配路径或子节点需要展开的路径\n" +
                "            const shouldShow = Array.from(matchPaths).some(p =>\n" +
                "                node.path === p || node.path.startsWith(p + '/')\n" +
                "            );\n" +
                "\n" +
                "            if (shouldShow) {\n" +
                "                acc.push(node);\n" +
                "                // 如果当前节点需要展开，继续处理子节点\n" +
                "                if (node.children && state.expandedCodes.has(node.path)) {\n" +
                "                    acc.push(...filterAndFlatten(node.children, matchPaths));\n" +
                "                }\n" +
                "            }\n" +
                "            return acc;\n" +
                "        }, []);\n" +
                "    }\n" +
                "\n" +
                "    // 查找匹配路径的方法\n" +
                "    function findMatchPaths(nodes, keyword) {\n" +
                "        const matchPaths = new Set();\n" +
                "        const expandPaths = new Set();\n" +
                "        const allChildrenCache = new Map();\n" +
                "\n" +
                "        // 预缓存所有节点的子路径\n" +
                "        function cacheChildrenPaths(node) {\n" +
                "            const children = [];\n" +
                "\n" +
                "            function traverse(n) {\n" +
                "                children.push(n.path);\n" +
                "                if (n.children) n.children.forEach(traverse);\n" +
                "            }\n" +
                "\n" +
                "            traverse(node);\n" +
                "            allChildrenCache.set(node.path, children);\n" +
                "            return children;\n" +
                "        }\n" +
                "\n" +
                "        function search(nodes) {\n" +
                "            let hasAnyMatch = false;\n" +
                "            nodes.forEach(node => {\n" +
                "                let currentMatch = false;\n" +
                "                const searchFields = node.isLeaf ?\n" +
                "                    ['code', 'message', 'userTip', 'className', 'enumName', 'messageKey'] :\n" +
                "                    ['display', 'configName', 'path'];\n" +
                "\n" +
                "                // 匹配逻辑\n" +
                "                currentMatch = searchFields.some(field => {\n" +
                "                    const value = node.isLeaf ?\n" +
                "                        node.originalData[field] :\n" +
                "                        node[field];\n" +
                "                    return String(value || '').includes(keyword);\n" +
                "                });\n" +
                "\n" +
                "                // 当匹配到父节点时，记录所有子路径\n" +
                "                if (currentMatch && !node.isLeaf) {\n" +
                "                    const childrenPaths = cacheChildrenPaths(node);\n" +
                "                    childrenPaths.forEach(p => matchPaths.add(p));\n" +
                "                }\n" +
                "\n" +
                "                // 递归子节点\n" +
                "                let childMatch = false;\n" +
                "                if (node.children) {\n" +
                "                    childMatch = search(node.children);\n" +
                "                }\n" +
                "\n" +
                "                if (currentMatch || childMatch) {\n" +
                "                    // 收集当前匹配路径及其父路径\n" +
                "                    if (currentMatch) {\n" +
                "                        matchPaths.add(node.path);\n" +
                "                        getAllParentPaths(node.path).forEach(p => {\n" +
                "                            expandPaths.add(p);\n" +
                "                            matchPaths.add(p);\n" +
                "                        });\n" +
                "                    }\n" +
                "                    hasAnyMatch = true;\n" +
                "                }\n" +
                "                return currentMatch || childMatch;\n" +
                "            });\n" +
                "            return hasAnyMatch;\n" +
                "        }\n" +
                "\n" +
                "        search(nodes);\n" +
                "        return {\n" +
                "            matchPaths: new Set([...matchPaths, ...expandPaths]),\n" +
                "            expandPaths\n" +
                "        };\n" +
                "    }\n" +
                "\n" +
                "    function getAllChildrenPaths(node) {\n" +
                "        const paths = [];\n" +
                "\n" +
                "        function traverse(n) {\n" +
                "            paths.push(n.path);\n" +
                "            if (n.children) {\n" +
                "                n.children.forEach(child => traverse(child));\n" +
                "            }\n" +
                "        }\n" +
                "\n" +
                "        traverse(node);\n" +
                "        return paths;\n" +
                "    }\n" +
                "\n" +
                "    // 获取所有父路径的方法（新增）\n" +
                "    function getAllParentPaths(path) {\n" +
                "        const parents = [];\n" +
                "        let currentPath = '';\n" +
                "        let remaining = path;\n" +
                "\n" +
                "        hierarchyConfig.forEach(config => {\n" +
                "            if (remaining.length <= 0) return;\n" +
                "            const seg = remaining.substr(0, config.length);\n" +
                "            currentPath += seg;\n" +
                "            parents.push(currentPath);\n" +
                "            remaining = remaining.substr(config.length);\n" +
                "        });\n" +
                "\n" +
                "        return parents.slice(0, -1); // 排除自身路径\n" +
                "    }    // 带过滤条件的扁平化方法\n" +
                "    function flattenTreeWithFilter(nodes, matchPaths, level = 0, parentVisible = true) {\n" +
                "        return nodes.reduce((acc, node) => {\n" +
                "            const shouldShow = Array.from(matchPaths).some(p =>\n" +
                "                p === node.path || p.startsWith(node.path)\n" +
                "            );\n" +
                "\n" +
                "            const shouldExpand = state.expandedCodes.has(node.path) ||\n" +
                "                state.forceExpandPaths.has(node.path);\n" +
                "\n" +
                "            if (shouldShow) {\n" +
                "                acc.push({...node, level});\n" +
                "\n" +
                "                if (node.children && shouldExpand) {\n" +
                "                    acc.push(...flattenTreeWithFilter(node.children, matchPaths, level + 1));\n" +
                "                }\n" +
                "            }\n" +
                "            return acc;\n" +
                "        }, []);\n" +
                "    }\n" +
                "\n" +
                "    function setupEventListeners() {\n" +
                "        document.getElementById('search').addEventListener('input',\n" +
                "            debounce(handleSearch, 300));\n" +
                "    }\n" +
                "\n" +
                "    function debounce(fn, delay) {\n" +
                "        let timer;\n" +
                "        return (...args) => {\n" +
                "            clearTimeout(timer);\n" +
                "            timer = setTimeout(() => fn(...args), delay);\n" +
                "        };\n" +
                "    }\n" +
                "\n" +
                "    // 启动应用\n" +
                "    document.addEventListener('DOMContentLoaded', init);\n");
        sb.append("</script>\n");
    }

    private String toJsonErrorCodeDoc(List<ErrorCodeDoc> docs) {
        StringBuilder sb = new StringBuilder("[");
        for (ErrorCodeDoc doc : docs) {
            if (sb.length() > 1) {
                sb.append(",");
            }
            sb.append("{")
                    .append("\"code\":\"").append(escapeJson(doc.getCode())).append("\",")
                    .append("\"enumName\":\"").append(escapeJson(doc.getEnumName())).append("\",")
                    .append("\"className\":\"").append(escapeJson(doc.getClassName())).append("\",")
                    .append("\"message\":\"").append(escapeJson(doc.getMessage())).append("\",")
                    .append("\"userTip\":\"").append(escapeJson(doc.getUserTip())).append("\",")
                    .append("\"messageKey\":\"").append(escapeJson(doc.getMessageKey())).append("\"")
                    .append("}");
        }
        sb.append("]");
        return sb.toString();
    }

    private String toJson(List<HierarchyConfig> hierarchy) {
        StringBuilder sb = new StringBuilder("[");
        for (HierarchyConfig config : hierarchy) {
            if (sb.length() > 1) {
                sb.append(",");
            }
            sb.append("{")
                    .append("\"name\":\"").append(escapeJson(config.getName())).append("\",")
                    .append("\"length\":").append(config.getLength()).append(",")
                    .append("\"mapping\":").append(mapToJson(config.getMapping()))
                    .append("}");
        }
        return sb.append("]").toString();
    }

    private String mapToJson(Map<?, ?> map) {
        if (map == null) {
            return "null";
        }

        StringBuilder sb = new StringBuilder("{");
        boolean first = true;

        for (Map.Entry<?, ?> entry : map.entrySet()) {
            if (!first) {
                sb.append(",");
            }

            String key = String.valueOf(entry.getKey());
            String value = String.valueOf(entry.getValue());

            sb.append("\"")
                    .append(escapeJson(key))
                    .append("\":\"")
                    .append(escapeJson(value))
                    .append("\"");

            first = false;
        }

        return sb.append("}").toString();
    }

    // 处理JSON特殊字符转义
    private String escapeJson(String input) {
        if (input == null) {
            return "";
        }
        return input.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\b", "\\b")
                .replace("\f", "\\f")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }
}